#ifndef YAML_H
#define YAML_H

#include <iostream>
#include <unordered_map>
#include <fstream>
#include <vector>
#include <string>
#include <array>
#include <exception>
#include <stack>

namespace NahidaProject {
	namespace YAML {
		enum __declspec(dllexport) YAMLType { noneType, intType, doubleType, stringType, arrayType, boolType, objectType, };
		std::string YAMLTypeToString(YAMLType type);

		class __declspec(dllexport) TypeMismatchException : public std::exception{
		private:
			std::string message;

		public:
			TypeMismatchException(const char* msg) : message(msg) {

			}

			const char* what() const throw(){
				return message.c_str();
			}
		};

		typedef __declspec(dllexport) struct { std::string name; YAMLType type; void* value;} NamedValue;

		class __declspec(dllexport) TypedValue{
		public:
			YAMLType type;
			void* value;

			TypedValue(){
				type = YAMLType::noneType;
				value = nullptr;
			}

			template<typename T> T cast(){
				switch (type){
				case YAMLType::intType:
					if (!std::is_same<T, int>::value && !std::is_same<T, double>::value)
						throw TypeMismatchException("Attempting to cast Integer value to an incompatible type");
					break;
				case YAMLType::doubleType:
					if (!std::is_same<T, double>::value)
						throw TypeMismatchException("Attempting to cast Double value to an incompatible type. If you are trying to cast to a float, cast to a Double first. Example: static_cast<float>(yaml_file[\"data\"].cast<double>())");
					break;
				case YAMLType::stringType:
					if (!std::is_same<T, std::string>::value)
						throw TypeMismatchException("Attempting to cast String value to an incompatible type");
					break;
				case YAMLType::arrayType:
					if (!std::is_same<T, std::vector<TypedValue>>::value)
						throw TypeMismatchException("Attempting to cast Array value to an incompatible type");
					break;
				case YAMLType::boolType:
					if (!std::is_same<T, bool>::value)
						throw TypeMismatchException("Attempting to cast Bool value to an incompatible type");
					break;
				case YAMLType::objectType:
					if (!std::is_same<T, std::unordered_map<std::string, TypedValue>>::value)
						throw TypeMismatchException("Attempting to cast Object value to an incompatible type");
					break;
				}

				return *(static_cast<T*>(value));
			}

			size_t size();
			TypedValue operator[](size_t index);
			TypedValue operator[](std::string name);
		}; 

		typedef __declspec(dllexport) std::vector<TypedValue> YAMLArray;
		typedef __declspec(dllexport) std::unordered_map<std::string, TypedValue> YAMLMap;

		typedef __declspec(dllexport) std::string String;
		typedef __declspec(dllexport) YAMLArray Array;
		typedef __declspec(dllexport) YAMLMap Object;

		int CountSpaces(const std::string& str, int tabSpaces = 4);
		bool IsVariableValuePair(const std::string& str);
		bool IsValid(const NamedValue& val);
		void DeleteValues(const TypedValue& v);
		std::string TruncateSpaces(const std::string& str);
		std::string TruncateSpacesAfter(const std::string& str);
		std::string RemoveSpacesAfterChar(const std::string& str, char c);
		std::string Filter(const std::string& str, char c);
		std::array<std::string, 2> SplitVariable(const std::string& str);
		std::vector<std::string> SplitArray(const std::string& str);
		YAMLType GetType(const std::string& str);
		YAMLMap Parse(std::string path);
		NamedValue StringToValue(const std::string& str);
		
		class __declspec(dllexport) YAML{
		public:
			YAMLMap data;

			YAML(std::string path);
			~YAML();
			size_t size();
			TypedValue operator[](std::string name);
		};
	}
}

#endif